home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / divisions < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  52.8 KB  |  1,486 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # Change the gawk path if you do not have it in /usr/local/bin.
  4. # You should use gawk 2.15.5 or later.
  5. # Put the awk line on top if you do not have gawk.
  6. # You will then have to precede options with "+" instead of "-".
  7. # This program writes to /dev/stderr, so you need to either use gawk or
  8. # have the dup driver installed.
  9. # @(#) divisions.gawk 2.4 97/06/19
  10. # 92/08/16 john h. dubois iii (spcecdt@armory.com)
  11. # 93/03/16 Work with either /dev/hd* or /dev/dsk/* division names
  12. # 93/07/11 Added warning about undivvied partition.
  13. # 93/10/20 Added printing of partition params in blocks as well as tracks
  14. # 93/10/29 Added help & physical layout sorting
  15. # 94/03/08 Use gawk so - options can be given.
  16. # 94/08/25 added u option.
  17. # 94/11/09 Exit 0 for gawk after dparam & fdisk; check their output.
  18. # 95/08/21 Use -b to check for existance of /dev/hdxx, not -f!
  19. #          Print partition device names; deal with nonexistant partition names.
  20. #          Added c option.
  21. # 95/08/26 Added C option; turn c option on by default; added processing of
  22. #          rcfile; print driver, unit, & SCSI params
  23. # 95/09/14 v5 port: added HTFS and DTFS to list of division types that are
  24. #          considered filesystems; redirect df input from /dev/null to work
  25. #          around protlib/gawk bug.
  26. # 96/09/09 Print driver major number in addition to driver name.
  27. #          Get driver name from the name of the open routine given in bdevsw[].
  28. #          Print SCSI bus number.
  29. # 96/10/01 Added n option.  
  30. #          Read /etc/ps/booted.system to get namelist file if -n is not given.
  31. # 97/06/19 Ignore drive if dparam on it fails.
  32.  
  33. BEGIN {
  34.     ProgName = "divisions"
  35.     Usage = "Usage: " ProgName " [-cChsux] [-n<namelist>]"
  36.     rcFile = "/etc/default/divisions"
  37.     ARGC = Opts(ProgName,Usage,"cCsuxn:h",0,rcFile,
  38.     "FSINFO,NOFSINFO,PSORT,BOUNDS_CHECK,DEBUG,NAMELIST")
  39.     if ("h" in Options) {
  40.     printf \
  41. "%s: display information about disks, partitions and divisions.\n"\
  42. "%s\n"\
  43. "Options:\n"\
  44. "Some of the following options can also be set by assigning values to\n"\
  45. "variables in a configuration file named %d.  Variables are assigned to\n"\
  46. "with the syntax:  varname=value  or in the case of flags, by simply\n"\
  47. "putting the indicated variable name in the file without a value.  Flag\n"\
  48. "options can be turned off on the command line by following them\n"\
  49. "immediately with \"-\", e.g. -n- to turn off the n option in such a way\n"\
  50. "that it cannot be turned on in the config file.  Variable names appear in\n"\
  51. "parentheses in the option descriptions.\n"\
  52. "Options:\n"\
  53. "-c: For divisions that contain filesystems, show filesystem size and space\n"\
  54. "    free (default).  (FSINFO)\n"\
  55. "-C: Turn off the -c option.  (NOFSINFO)\n"\
  56. "-h: Print this help.\n"\
  57. "-n<namelist>: Use <namelist> as the symbol file used to access the system\n"\
  58. "    image.  This is used only to get the driver and unit identification.\n"\
  59. "    (NAMELIST)\n"\
  60. "-s: Sort output by physical disk layout instead of partition and division\n"\
  61. "    number.  (PSORT)\n"\
  62. "-u: Report space in UNIX & XENIX partitions not used by any division, and\n"\
  63. "    overlapping divisions.  This turns on the s option.  (BOUNDS_CHECK)\n"\
  64. "-x: Turn on debugging (show command output, etc.).  (DEBUG)\n",
  65.     ProgName,Usage,rcFile
  66.     exit 0
  67.     }
  68.     Debug = "x" in Options
  69.     ShowContents = !("C" in Options)
  70.     FindUnusedPart = "u" in Options
  71.     Sort = FindUnusedPart || "s" in Options
  72.     if ("n" in Options)
  73.     Namelist = Options["n"]
  74.  
  75.     if (!FindDrives(Drives,DriveDevs)) {
  76.     print "Error getting drive list.  Exiting." > "/dev/stderr"
  77.     exit 1
  78.     }
  79.     GetParams(DriveDevs,Cylinders,Heads,SPT)
  80.     PrintParams(Drives,DriveDevs,Cylinders,Heads,SPT,Namelist)
  81.     GetPartitions(DriveDevs,Start,End,Size,Type,Status,PDev)
  82.     PrintPartitions(SPT,Drives,PDev,Status,Type,Start,End,Sort,PartMap)
  83.     GetDivisions(Type,PDev,Name,DivType,FirstBlock,LastBlock)
  84.     if (ShowContents)
  85.     GetContents(Name,DivType,fsSize,fsUsed)
  86.  
  87.     PrintDivisions(Drives,Start,Name,DivType,FirstBlock,LastBlock,Sort,PartMap,
  88.     FindUnusedPart,ShowContents,fsSize,fsUsed)
  89. }
  90.  
  91. # Drives[1..n] gives drive number.
  92. # All other arrays are indexed by drive number.
  93. function PrintParams(Drives,DriveDevs,Cylinders,Heads,SPT,
  94. Format,i,DriveNum,Dev,Cyl,Hds,Sec,Majors,Minors,Drivers,Unit,HAtype,HAnumber,
  95. ID,LUN,Ind,Bus,devTypes) {
  96.     Format = "%2s %4s %5s %3s %6s %8s %-7s %-9s %5s"
  97.     SCSIformat = " %-4s%6s %2s %3s %3s\n"
  98.     printf Format,"HD","Cyl","Heads","SPT","Tracks","Size(MB)","Device",
  99.    "Driver","Unit"
  100.     printf SCSIformat,"HA","HA-num","ID","LUN","Bus"
  101.     for (i = 1; i in Drives; i++)
  102.     Majors[DriveDevs[Drives[i]]]
  103.     GetDevInfo(Drivers,Majors,Minors,devTypes,Namelist)
  104.     Read_mscsi(HAtype,HAnumber,ID,LUN,Bus)
  105.     for (i = 1; i in Drives; i++) {
  106.     DriveNum = Drives[i]
  107.     if (!(DriveNum in Cylinders))
  108.         continue
  109.     Dev = DriveDevs[DriveNum]
  110.     Cyl = Cylinders[DriveNum]
  111.     Hds = Heads[DriveNum]
  112.     Sec = SPT[DriveNum]
  113.     Unit = int(Minors[Dev]/64)
  114.     printf Format,DriveNum,Cyl,Hds,Sec,Cyl*Hds,int(Cyl*Hds*Sec/2048),
  115.     substr(Dev,7),sprintf("%3s",(Dev in Majors) ? Majors[Dev] : "?") \
  116.     "/" ((Dev in Drivers) ? Drivers[Dev] : "?"),Unit
  117.     if (Drivers[Dev] == "Sdsk") {
  118.         Ind = "Sdsk" SUBSEP Unit
  119.         printf SCSIformat,HAtype[Ind],HAnumber[Ind],ID[Ind],LUN[Ind],
  120.         Bus[Ind]
  121.     }
  122.     else
  123.         printf SCSIformat,"-","-","-","-","-"
  124.     }
  125.     print ""
  126. }
  127.  
  128. # Input variables:
  129. # Names[DriveNum,Part,Div]: Division names (from device nodes).
  130. # DivType[DriveNum,Part,Div]: Filesystem types for each division.
  131. # Output variables:
  132. # fsSize[DriveNum,Part,Div] = filesystem size, in 1K blocks.
  133. # fsUsed[DriveNum,Part,Div] = filesystem space used, in 1K blocks.
  134. function GetContents(Names,DivType,fsSize,fsUsed,
  135. fsTypes,Cmd,Lines,Dev,Name2Ind,Elem,Ind,i) {
  136.     MakeSet(fsTypes,"AFS,EAFS,S51K,HTFS,DTFS",",")
  137.     for (i in DivType) {
  138.     if (DivType[i] in fsTypes)
  139.         # Must put leading /dev/ back on due to df bug
  140.         Cmd = Cmd " /dev/" Names[i]
  141.     # Make index to retrieve info from output with
  142.     Name2Ind["/dev/" Names[i]] = i
  143.     }
  144.     if (Cmd == "")
  145.     return
  146.     # Redirect input from /dev/null to work around gawk+protlib bugs
  147.     Cmd = "df -v " Cmd " < /dev/null"
  148.     if (Debug)
  149.     ErrPrint("Getting filesystem info with command: " Cmd)
  150.     ReadLines(Cmd,"",2," +",Lines)
  151.     # df output looks like this (first field is a df bug):
  152.     #   1           2                      3         4
  153.     # Mount Dir  Filesystem              blocks      used      free   %used
  154.     # /dev/u     /dev/u                 2400000   2252262    147738    93%
  155.     # or, if fs is not mounted:
  156.     #            /dev/u                 2400000   2252262    147738    93%
  157.     for (Dev in Lines)
  158.     if (Dev in Name2Ind) {
  159.         Ind = Name2Ind[Dev]
  160.         # Must split on " +" so that if 1st field is empty it will still
  161.         # be the 1st field.
  162.         split(Lines[Dev],Elem," +")
  163.         fsSize[Ind] = int(Elem[3]/2)
  164.         fsUsed[Ind] = int(Elem[4]/2)
  165.         if (Debug)
  166.         ErrPrint(\
  167.         Dev ": size = " fsSize[Ind] "; block used = " fsUsed[Ind])
  168.     }
  169. }
  170.  
  171. # Input variables:
  172. # SPT[]: Sectors per track
  173. # Drives[1..n]: Drive numbers that exist.
  174. # PDev[DriveNum,Partition]: Partition device names.
  175. # Status[DriveNum,Partition]: Partition status.
  176. # Type[DriveNum,Partition]: Partition type.
  177. # Start[DriveNum,Partition]: Partition start track.
  178. # End[DriveNum,Partition]: Partition end track.
  179. # PSort: Whether to sort partitions by starting track.
  180. # Output variables:
  181. # PartMap[DriveNum,1..n]: Set to the partition numbers in the order they were
  182. # printed (affected by PSort).
  183. function PrintPartitions(SPT,Drives,PDev,Status,Type,Start,End,PSort,PartMap,
  184. Sec,Format,Drive,DriveNum,Partition,i,Map,Num,k,TW) {
  185.     #         HD  Par Stat Type sTr eTr zTr sBl eBl zBl
  186.     TW = 6    # Width of track fields
  187.     Format = "%2s %4s %-8s %-5s %" TW "s %" TW "s %" TW "s %8s %8s %8s %s\n"
  188.     printf Format,"HD","Part","Status","Type","StartT","EndT","SizeT",
  189.     "StartB","EndB","SizeB","Device"
  190.     for (Drive = 1; Drive in Drives; Drive++) {
  191.     DriveNum = Drives[Drive]
  192.     split("",Map,"")
  193.     Num = 0
  194.     for (Partition = 1; Partition <= 4; Partition++)
  195.         if (DriveNum SUBSEP Partition in Start)
  196.         if (PSort)
  197.             Map[Partition] = Start[DriveNum,Partition]
  198.         else
  199.             k[++Num] = Partition
  200.     if (PSort)
  201.         Num = qsortArbIndByValue(Map,k)
  202.     for (i = 1; i <= Num; i++) {
  203.         PartMap[DriveNum,i] = Partition = k[i]
  204.         Sec = SPT[DriveNum]
  205.         printf Format,DriveNum,Partition,Status[DriveNum,Partition],
  206.         Type[DriveNum,Partition],
  207.         Start[DriveNum,Partition],
  208.         End[DriveNum,Partition],Size[DriveNum,Partition],
  209.         Start[DriveNum,Partition] * Sec,
  210.         End[DriveNum,Partition] * Sec,Size[DriveNum,Partition] * Sec,
  211.         ((DriveNum,Partition) in PDev) ? PDev[DriveNum,Partition] : "-"
  212.     }
  213.     }
  214.     print ""
  215. }
  216.  
  217. function PrintDivisions(Drives,Start,Name,DivType,FirstBlock,LastBlock,PSort,
  218. PartMap,FindUnusedPart,ShowContents,fsSize,fsUsed,
  219. Division,Partition,DriveNum,Format,Drive,Map,k,i,Num,PartNum,StartB,EndB,
  220. PrevEnd,PrevName,Size,Used,Pct) {
  221.     Format = "%2s %4s %3s %-13s %-8s %7s %7s %7s"
  222.     if (ShowContents)
  223.     Format = Format " %7s %7s %4s"
  224.     Format = Format "\n"
  225.     printf \
  226.     Format,"HD","Part","Div","Name","Type","Start","End","Size","FS-size",
  227.     "FS-used","%"
  228.     for (Drive = 1; Drive in Drives; Drive++) {
  229.     DriveNum = Drives[Drive]
  230.     for (PartNum = 1; (DriveNum SUBSEP PartNum) in PartMap; PartNum++) {
  231.         Partition = PartMap[DriveNum,PartNum]
  232.         if ((DriveNum SUBSEP Partition) in Start) {
  233.         split("",Map,"")
  234.         Num = 0
  235.         for (Division = 0; Division <= 6; Division++)
  236.             if (DriveNum SUBSEP Partition SUBSEP Division in Name)
  237.             if (PSort)
  238.                 Map[Division] = \
  239.                 FirstBlock[DriveNum,Partition,Division]
  240.             else
  241.                 k[++Num] = Division
  242.  
  243.         if (FindUnusedPart) {
  244.             PrevEnd = -1
  245.             PrevName = "start of partition"
  246.         }
  247.         if (PSort)
  248.             Num = qsortArbIndByValue(Map,k)
  249.         for (i = 1; i <= Num; i++) {
  250.             Division = k[i]
  251.             StartB = FirstBlock[DriveNum,Partition,Division]
  252.             EndB = LastBlock[DriveNum,Partition,Division]
  253.             DivName = Name[DriveNum,Partition,Division]
  254.             if (FindUnusedPart) {
  255.             if (StartB > (PrevEnd + 1))
  256.                 printf \
  257.             "* %d blocks unused (%d - %d) between %s and %s.\n",
  258.                 StartB-PrevEnd-1,PrevEnd+1,StartB-1,
  259.                 PrevName,DivName
  260.             if (StartB < (PrevEnd + 1))
  261.                 printf \
  262.                 "!!! Divisions %s and %s overlap by %d blocks!\n",
  263.                 PrevName,DivName,PrevEnd-StartB+1
  264.             PrevEnd = EndB
  265.             PrevName = DivName
  266.             }
  267.  
  268.             if ((DriveNum,Partition,Division) in fsSize) {
  269.             Size = fsSize[DriveNum,Partition,Division]
  270.             Used = fsUsed[DriveNum,Partition,Division]
  271.             Pct = Percent4(Used/Size)
  272.             }
  273.             else
  274.             Pct = Used = Size = "-"
  275.             printf Format,DriveNum,Partition,Division,DivName,
  276.             DivType[DriveNum,Partition,Division],
  277.             StartB, EndB, EndB - StartB + 1,
  278.             Size,Used,Pct
  279.         }
  280.  
  281.         # Report space at end of partition as unallocated even though
  282.         # some is used by system because there is no obvious way of
  283.         # determining how much is used by system & how much is really
  284.         # unused.
  285.         if (FindUnusedPart && \
  286.         (StartB = (LastBlock[DriveNum,Partition,7]+1)) > PrevEnd+1)
  287.             printf \
  288.             "* %d blocks unused (%d - %d) between %s and %s.\n",
  289.             StartB-PrevEnd-1,PrevEnd+1,StartB-1,
  290.             PrevName,"end of partition"
  291.  
  292.         }
  293.     }
  294.     }
  295. }
  296.  
  297. # Find all /dev/rhd?0 nodes
  298. # Set Drives[1..n] to the number of each drive
  299. # For each node, set DriveDevs[drivenum] to the device name
  300. function FindDrives(Drives,DriveDevs,  DriveInd,Drive) {
  301.     DriveInd = 1
  302.     if (CmdReadLine("echo /dev/rhd?0",1) != 1)
  303.     return 0
  304.     for (Drive = 1; Drive <= NF; Drive++) {
  305.     DriveNum = substr($Drive,9,1)
  306.     Drives[DriveInd++] = DriveNum
  307.     DriveDevs[DriveNum] = $Drive
  308.     }
  309.     return 1
  310. }
  311.  
  312. # Do an fdisk on each drive in DriveDevs
  313. # Set the following arrays to drive data, indexed by drive number & partition:
  314. # Start        First track
  315. # End        Last track
  316. # Size        Total tracks
  317. # Type        Partition type
  318. # Status    Partition status
  319. # PDev        Partition device name
  320. function GetPartitions(DriveDevs,Start,End,Size,Type,Status,PDev,
  321. cmd,DriveInd,Partition,DriveNum,DriveDev,PInfo,line,NameCmd) {
  322.     for (DriveNum in DriveDevs) {
  323.     # exit 0 for gawk
  324.     #cmd = "/etc/fdisk -p -f " DriveDevs[DriveNum] " 2>/dev/null; exit 0"
  325.     cmd = "exec /etc/fdisk -p -f " DriveDevs[DriveNum] " 2>/dev/null"
  326.     if (Debug)
  327.         ErrPrint("Getting partition info with: " cmd)
  328.     while ((cmd | getline) == 1) {
  329.         if (Debug)
  330.         ErrPrint("fdisk output: " $0)
  331.         # Some error messages, e.g. "cannot open /dev/rhd10", are currently
  332.         # printed to stdout.  Only pay attention to lines with 6 fields.
  333.         if (NF >= 6) {
  334.         Partition = $1
  335.         Start[DriveNum,Partition] = $2
  336.         End[DriveNum,Partition] = $3
  337.         Size[DriveNum,Partition] = $4
  338.         Type[DriveNum,Partition] = $5
  339.         Status[DriveNum,Partition] = $6
  340.  
  341.         DevC = $6 == "Active" ? "a" : Partition
  342.         NameCmd = NameCmd "\n"\
  343. "n=" DriveNum " p=" Partition " c=" DevC "\n"\
  344. "for dev in hd$n$c dsk/${n}s$c; do [ -b $dev ] &&\n"\
  345. "{ echo $n $p $dev; break; }; done"
  346.         }
  347.     }
  348.     close(cmd)
  349.     }
  350.     NameCmd = "cd /dev" NameCmd
  351.     if (Debug)
  352.     ErrPrint("Getting partition names with:\n" NameCmd)
  353.     while ((NameCmd | getline) == 1)
  354.     if (NF == 3)
  355.         PDev[$1,$2] = $3
  356.     else
  357.         print "Bad partition name output: " $0 > "/dev/stderr"
  358.     close(NameCmd)
  359. }
  360.  
  361. # Reads mscsi file, and stores info in the arrays, indexed by
  362. # [device-type,unit-number], where device-type is the SCSI device type as
  363. # given in the 2nd field of mscsi, and unit-number is the instance of the
  364. # device, starting with 0.  mscsi has this format:
  365. #   1          2          3        4     5    6
  366. # HA-type device-type HA-number SCSI-ID LUN    Bus
  367. # Bus is not given in versions that do not support multiple adapter buses.
  368. function Read_mscsi(HAtype,HAnumber,ID,LUN,Bus,
  369. mscsi,ret,instance,i) {
  370.     mscsi = "/etc/conf/cf.d/mscsi"
  371.     FS = "[ \t]+"
  372.     while ((ret = (getline < mscsi)) == 1)
  373.     if ($0 !~ /^\*/ && NF >= 5) {
  374.         i = $2 SUBSEP instance[$2]++ + 0
  375.         #printf "%s:%s:%s:%s\n",$1,$3,$4,$5
  376.         HAtype[i] = $1
  377.         HAnumber[i] = $3
  378.         ID[i] = $4
  379.         LUN[i] = $5
  380.         Bus[i] = (NF >= 6) ? $6 : 0
  381.     }
  382.     return ret
  383. }
  384.  
  385. # GetDevInfo: get information about device nodes.
  386. # Device nodes to get information about are passed as indexes of Majors[].
  387. # For each index in Majors[], sets these:
  388. # Majors[dev]: major number
  389. # Minors[dev]: minor number
  390. # devTypes[dev]: c or b (character or block)
  391. # Drivers[dev]: driver name, based on the name of the device open routine for
  392. #               the major number of the device (as reported by crash).
  393. function GetDevInfo(Drivers,Majors,Minors,devTypes,Namelist,
  394. Cmd,devName,Lines,Line,Major,Elem,Maj2Driver,type,dev,dev2maj) {
  395.     for (devName in Majors)
  396.     Cmd = Cmd " " devName
  397.     ReadLines("l -gon" Cmd,"",0,"[ ,]+",Lines)
  398.     for (devName in Lines)
  399.     if (devName in Majors && (Line = Lines[devName]) ~ /^[cb]/) {
  400.         split(Line,Elem,"[ ,]+")
  401.         Majors[devName] = Major = Elem[3]
  402.         Minors[devName] = Elem[4]
  403.         devTypes[devName] = type = substr(Line,1,1)
  404.         Maj2Driver[Major,type]
  405.         dev2maj[devName] = Major SUBSEP type
  406.     }
  407.     driverNames(Maj2Driver,Namelist)
  408.     for (dev in Majors)
  409.     Drivers[dev] = Maj2Driver[dev2maj[dev]]
  410. }
  411.  
  412. # This relies on hardcoded sizes for the bdevsw and cdevsw structs.  The
  413. # commands passed to crash only work with the crash shipped with 5.0 & later.
  414. function driverNames(maj2Driver,Namelist,
  415. major,crashStr,Cmd,Order,i,Driver,type) {
  416.     # Get list of all major numbers that driver names are needed for
  417.     i = 0
  418.     for (t in maj2Driver) {
  419.     split(t,Elem,SUBSEP)
  420.     major = Elem[1]
  421.     type = Elem[2]
  422.     Order[i++] = t
  423.     crashStr = crashStr sprintf("ts *(" type "devsw+%x)\n",
  424.     major*(type == "b" ? 24 : 32))
  425.     }
  426.     if (Namelist == "") {
  427.     ReadConfigFile(Values,Lines,"/etc/ps/booted.system","#","=",1)
  428.     Namelist = Values["KERNEL"]
  429.     }
  430.     if (Namelist != "")
  431.     Namelist = " -n " Namelist
  432.     Cmd = "echo '" crashStr "' 2>&1 | crash" Namelist
  433.     i = 0
  434.     while ((Cmd | getline) == 1 && (i in Order)) {
  435.     # Get rid of header.  Must do it here rather than immediately after
  436.     # starting coprocess because new crash does not print the header until
  437.     # the first line of other output.
  438.     if ($0 ~ /^dumpfile/)
  439.         continue
  440.     if ($0 ~ /^Warning/) {
  441.         print $0 > "/dev/stderr"
  442.         continue
  443.     }
  444.     # Old crash will have a prompt as field 1, but since it will not work
  445.     # here anyway, do not worry about it.
  446.     if ($0 ~ /^[a-zA-Z0-9_]*open +\+ 0$/) {
  447.         Driver = $1
  448.         sub("open$","",Driver)
  449.     }
  450.     else
  451.         Driver = "?"
  452.     maj2Driver[Order[i]] = Driver
  453.     i++
  454.     }
  455.     close(Cmd)
  456. }
  457.  
  458. # Drivers is set to the driver name given in mdevice for the major
  459. # number that the device has.  Note that this may be incorrect if mdevice is
  460. # out of sync with the booted kernel.
  461. # mdevice has this format:
  462. #        1       2        3           4      5      6     7     8   9
  463. # driver-name funcs characteristics prefix bmajor cmajor minu maxu dma
  464. function getmdevice(char,block,  mdevice,driver,Elem) {
  465.     # Index by driver name because that is the field that is required to be
  466.     # unique.  Cannot index by bmajor or cmajor because some devices will have
  467.     # one but not both.
  468.     ReadLines("","/etc/conf/cf.d/mdevice",1,"[ \t]+",mdevice)
  469.     for (driver in mdevice) {
  470.     split(mdevice[driver],Elem,"[ \t]+")
  471.     block[Elem[5]] = driver
  472.     char[Elem[6]] = driver
  473.     }
  474. }
  475.  
  476. # Input vars:
  477. # Type[DriveNum,Partition]: Partition type (e.g. UNIX).
  478. # PDev[DriveNum,Partition]: Partition device names
  479. # Output vars:
  480. # For each XENIX or UNIX partition in Type,
  481. # do a divvy to get division info & put it in the following arrays:
  482. # Name[DriveNum,Part,Div]: division names (from device nodes).
  483. # DivType[DriveNum,Part,Div]: division types (e.g. AFS, EAFS, NON FS)
  484. # FirstBlock[DriveNum,Part,Div]: Partition block that division starts on.
  485. # LastBlock[DriveNum,Part,Div]: Partition block that division ends on.
  486. function GetDivisions(Type,PDev,Name,DivType,FirstBlock,LastBlock,
  487. DrivePart,DriveNum,Partition,DInfo,Division,line) {
  488.     for (DrivePart in Type)
  489.     if (Type[DrivePart] ~ "XENIX|UNIX") {
  490.         split(DrivePart,Elem,SUBSEP)
  491.         DriveNum = Elem[1]
  492.         Partition = Elem[2]
  493.         if (!((DriveNum,Partition) in PDev)) {
  494.         printf "No device found for drive %d partition %d\n",
  495.         DriveNum,Partition > "/dev/stderr"
  496.         continue
  497.         }
  498.         # We have to echo commands into divvy because the -P option does
  499.         # not display the node names.  -N option is not available pre-5.0
  500.         cmd = "echo \"q\ne\"|divvy /dev/" PDev[DriveNum,Partition]
  501.         if (Debug)
  502.         ErrPrint("Getting division information with command:\n" cmd)
  503.         cmd | getline line
  504.         if (Debug) {
  505.         ErrPrint("Output:")
  506.         ErrPrint(line)
  507.         }
  508.         if (line ~ "No valid division table") {
  509.         close(cmd)
  510.         printf "NOTE: drive %d, partition %d is not divvied.\n",
  511.         DriveNum,Partition
  512.         continue
  513.         }
  514.         cmd | getline line
  515.         if (Debug)
  516.         ErrPrint(line)
  517.         cmd | getline line
  518.         if (Debug)
  519.         ErrPrint(line)
  520.         # 7th division info is used by -u option
  521.         for (Division = 0; Division <= 7; Division++) {
  522.         cmd | getline line
  523.         if (Debug)
  524.             ErrPrint(line)
  525.         split(line,DInfo," *\\| *")
  526.         if (DInfo[5] != Division)
  527.             printf \
  528.     "Bad divvy output for drive %d, partition %d, division %d:\n%s\n",
  529.             DriveNum,Partition,Division,line
  530.         else if (DInfo[3] != "NOT USED") {
  531.             Name[DriveNum,Partition,Division] = DInfo[2]
  532.             DivType[DriveNum,Partition,Division] = DInfo[3]
  533.             FirstBlock[DriveNum,Partition,Division] = DInfo[6]
  534.             LastBlock[DriveNum,Partition,Division] = DInfo[7]
  535.         }
  536.         }
  537.         close(cmd)
  538.     }
  539. }
  540.  
  541. function GetParams(DriveDevs,Cylinders,Heads,SPT,  Drive) {
  542.     for (DriveNum in DriveDevs) {
  543.     # exit 0 to avoid gawk warning.  Whether device could be opened can
  544.     # be determined by whether data was read from the command.
  545.     #CmdReadLine("dparam " DriveDevs[DriveNum] " 2>/dev/null; exit 0")
  546.     if (CmdReadLine("exec dparam " DriveDevs[DriveNum] " 2>/dev/null",1) \
  547.     != 1)
  548.         continue
  549.     if (NF > 7) {
  550.         Cylinders[DriveNum] = $1
  551.         Heads[DriveNum] = $2
  552.         SPT[DriveNum] = $8
  553.     }
  554.     }
  555. }
  556.  
  557. function ErrPrint(s) {
  558.     printf "* %s\n",s > "/dev/stderr"
  559. }
  560.  
  561. function Intersection(A,B,Inter,  Elem,Count) {
  562.     for (Elem in A)
  563.     if (Elem in B) {
  564.         Inter[Elem]
  565.         Count++
  566.     }
  567.     return Count
  568. }
  569.  
  570. function Union(A,B,Both,  Elem) {
  571.     for (Elem in A)
  572.     Both[Elem]
  573.     for (Elem in B)
  574.     Both[Elem]
  575. }
  576.  
  577. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  578. function SubtractSet(Minuend,Subtrahend,  Elem) {
  579.     for (Elem in Subtrahend)
  580.     delete Minuend[Elem]
  581. }
  582.  
  583. function CopySet(From,To,  Elem) {
  584.     for (Elem in From)
  585.     To[Elem]
  586. }
  587.  
  588. # Returns 1 if Set is empty, 0 if not.
  589. function IsEmpty(Set,  i) {
  590.     for (i in Set)
  591.     return 0
  592.     return 1
  593. }
  594.  
  595. # MakeSet: make a set from a list.
  596. # An index with the name of each element of the list
  597. # is created in the given array.
  598. # Input variables:
  599. # Elements is a string containing the list of elements.
  600. # Sep is the character that separates the elements of the list.
  601. # Output variables:
  602. # Set is the array.
  603. # Return value: the number of elements added to the set.
  604. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  605.     Num = split(Elements,Names,Sep)
  606.     for (i = 1; i <= Num; i++)
  607.     Set[Names[i]]
  608.     return Num
  609. }
  610. # Returns the number of elements in set Set
  611. function NumElem(Set,  elem,Num) {
  612.     for (elem in Set)
  613.     Num++
  614.     return Num
  615. }
  616.  
  617. # Occupy exactly 4 characters.
  618. function Percent4(Val) {
  619.     if (Val+0 > 0.999)
  620.     return " 100"
  621.     else
  622.     return sprintf("%4.1f",Val * 100)
  623. }
  624.  
  625. # @(#) CmdReadLine 95/09/04
  626. # Run Command, read a single line of output from it, then close it.
  627. # If Verbose is true, a complaint is issued if the read fails.
  628. # Output is returned in $*.  $* will not be changed if Command fails, so it is
  629. # important to check the return value of this function.
  630. # The return value from getline is returned.  It will be 1 on a successful
  631. # read; 0 if no lines were read due because the command produced no output
  632. # or could not be run.  ERRNO is never set since pipes are run by a shell.
  633. function CmdReadLine(Command,Verbose,  ret) {
  634.     if (Debug) {
  635.     print "* Issuing command: " Command "\n"\
  636.           "* Waiting for single line of output..." > "/dev/stderr"
  637.     }
  638.     ret = Command | getline
  639.     if (Verbose && ret != 1)
  640.     printf "Read from pipe \"%s\" failed\n",Command
  641.     # close does not return a value under awk, only gawk
  642.     close(Command)
  643.     if (Debug && !ret)
  644.     print "* Output: " $0 > "/dev/stderr"
  645.     return ret
  646. }
  647.  
  648. ### Start of ProcArgs library
  649. # @(#) ProcArgs 1.11 96/12/08
  650. # 92/02/29 john h. dubois iii (john@armory.com)
  651. # 93/07/18 Added "#" arg type
  652. # 93/09/26 Do not count -h against MinArgs
  653. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  654. #          Removed meaning of "+" or "-" by itself.
  655. # 94/03/08 Added & option and *()< option types.
  656. # 94/04/02 Added NoRCopt to Opts()
  657. # 94/06/11 Mark numeric variables as such.
  658. # 94/07/08 Opts(): Do not require any args if h option is given.
  659. # 95/01/22 Record options given more than once.  Record option num in argv.
  660. # 95/06/08 Added ExclusiveOptions().
  661. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  662. #          Expand $VARNAME at the start of its filenames.
  663. #          Let varname=0 and -option- turn off an option.
  664. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  665. #          of the vars should be searched for in the environment.
  666. #          Check for duplicate rcfiles.
  667. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  668. #          now return various negatives values on error, not just -1, and
  669. #          Opts() may set Err to various positive values, not just 1.
  670. #          Added AllowUnrecOpt.
  671. # 96/05/23 Check type given for & option
  672. # 96/06/15 Re-port to awk
  673. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  674. #          used by other functions.
  675. # 96/10/15 Added OptChars
  676. # 96/11/01 Added exOpts arg to Opts()
  677. # 96/11/16 Added ; type
  678. # 96/12/08 Added Opt2Set() & Opt2Sets()
  679. # 96/12/27 Added CmdLineOpt()
  680.  
  681. # optlist is a string which contains all of the possible command line options.
  682. # A character followed by certain characters indicates that the option takes
  683. # an argument, with type as follows:
  684. # :    String argument
  685. # ;    Non-empty string argument
  686. # *    Floating point argument
  687. # (    Non-negative floating point argument
  688. # )    Positive floating point argument
  689. # #    Integer argument
  690. # <    Non-negative integer argument
  691. # >    Positive integer argument
  692. # The only difference the type of argument makes is in the runtime argument
  693. # error checking that is done.
  694.  
  695. # The & option is a special case used to get numeric options without the
  696. # user having to give an option character.  It is shorthand for [-+.0-9].
  697. # If & is included in optlist and an option string that begins with one of
  698. # these characters is seen, the value given to "&" will include the first
  699. # char of the option.  & must be followed by a type character other than ":"
  700. # or ";".
  701. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  702.  
  703. # Strings in argv[] which begin with "-" or "+" are taken to be
  704. # strings of options, except that a string which consists solely of "-"
  705. # or "+" is taken to be a non-option string; like other non-option strings,
  706. # it stops the scanning of argv and is left in argv[].
  707. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  708. # If an option takes an argument, the argument may either immediately
  709. # follow it or be given separately.
  710. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  711. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  712. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  713. # this feature had a flaw that caused problems in some cases.  See the OptChars
  714. # parameter to explicitly set the option-specifier characters.
  715.  
  716. # If an option that does not take an argument is given,
  717. # an index with its name is created in Options and its value is set to the
  718. # number of times it occurs in argv[].
  719.  
  720. # If an option that does take an argument is given, an index with its name is
  721. # created in Options and its value is set to the value of the argument given
  722. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  723. # If an option that takes an argument is given more than once,
  724. # Options[option-name,"count"] is incremented, and the value is assigned to
  725. # the index (option-name,instance) where instance is 2 for the second occurance
  726. # of the option, etc.
  727. # In other words, the first time an option with a value is encountered, the
  728. # value is assigned to an index consisting only of its name; for any further
  729. # occurances of the option, the value index has an extra (count) dimension.
  730.  
  731. # The sequence number for each option found in argv[] is stored in
  732. # Options[option-name,"num",instance], where instance is 1 for the first
  733. # occurance of the option, etc.  The sequence number starts at 1 and is
  734. # incremented for each option, both those that have a value and those that
  735. # do not.  Options set from a config file have a value of 0 assigned to this.
  736.  
  737. # Options and their arguments are deleted from argv.
  738. # Note that this means that there may be gaps left in the indices of argv[].
  739. # If compress is nonzero, argv[] is packed by moving its elements so that
  740. # they have contiguous integer indices starting with 0.
  741. # Option processing will stop with the first unrecognized option, just as
  742. # though -- was given except that unlike -- the unrecognized option will not be
  743. # removed from ARGV[].  Normally, an error value is returned in this case.
  744. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  745. # be found, so the number of remaining arguments is returned instead.
  746. # If OptChars is not a null string, it is the set of characters that indicate
  747. # that an argument is an option string if the string begins with one of the
  748. # characters.  A string consisting solely of two of the same option-indicator
  749. # characters stops the scanning of argv[].  The default is "-+".
  750. # argv[0] is not examined.
  751. # The number of arguments left in argc is returned.
  752. # If an error occurs, the global string OptErr is set to an error message
  753. # and a negative value is returned.
  754. # Current error values:
  755. # -1: option that required an argument did not get it.
  756. # -2: argument of incorrect type supplied for an option.
  757. # -3: unrecognized (invalid) option.
  758. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  759. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  760. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  761. {
  762. # ArgNum is the index of the argument being processed.
  763. # ArgsLeft is the number of arguments left in argv.
  764. # Arg is the argument being processed.
  765. # ArgLen is the length of the argument being processed.
  766. # ArgInd is the position of the character in Arg being processed.
  767. # Option is the character in Arg being processed.
  768. # Pos is the position in OptList of the option being processed.
  769. # NumOpt is true if a numeric option may be given.
  770.     ArgsLeft = argc
  771.     NumOpt = index(OptList,"&")
  772.     OptionNum = 0
  773.     if (OptChars == "")
  774.     OptChars = "-+"
  775.     while (OptChars != "") {
  776.     c = substr(OptChars,1,1)
  777.     OptChars = substr(OptChars,2)
  778.     OptCharSet[c]
  779.     OptTerm[c c]
  780.     }
  781.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  782.     Arg = argv[ArgNum]
  783.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  784.         break    # Not an option; quit
  785.     if (Arg in OptTerm) {
  786.         delete argv[ArgNum]
  787.         ArgsLeft--
  788.         break
  789.     }
  790.     ArgLen = length(Arg)
  791.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  792.         Option = substr(Arg,ArgInd,1)
  793.         if (NumOpt && Option ~ /[-+.0-9]/) {
  794.         # If this option is a numeric option, make its flag be & and
  795.         # its option string flag position be the position of & in
  796.         # the option string.
  797.         Option = "&"
  798.         Pos = NumOpt
  799.         # Prefix Arg with a char so that ArgInd will point to the
  800.         # first char of the numeric option.
  801.         Arg = "&" Arg
  802.         ArgLen++
  803.         }
  804.         # Find position of flag in option string, to get its type (if any).
  805.         # Disallow & as literal flag.
  806.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  807.         if (AllowUnrecOpt) {
  808.             Escape = 1
  809.             break
  810.         }
  811.         else {
  812.             OptErr = "Invalid option: " specGiven Option
  813.             return -3
  814.         }
  815.         }
  816.  
  817.         # Find what the value of the option will be if it takes one.
  818.         # NeedNextOpt is true if the option specifier is the last char of
  819.         # this arg, which means that if the option requires a value it is
  820.         # the next arg.
  821.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  822.         if (GotValue = ArgNum + 1 < argc)
  823.             Value = argv[ArgNum+1]
  824.         }
  825.         else {    # Value is included with option
  826.         Value = substr(Arg,ArgInd + 1)
  827.         GotValue = 1
  828.         }
  829.  
  830.         if (HadValue = AssignVal(Option,Value,Options,
  831.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  832.         specGiven)) {
  833.         if (HadValue < 0)    # error occured
  834.             return HadValue
  835.         if (HadValue == 2)
  836.             ArgInd++    # Account for the single-char value we used.
  837.         else {
  838.             if (NeedNextOpt) {    # option took next arg as value
  839.             delete argv[++ArgNum]
  840.             ArgsLeft--
  841.             }
  842.             break    # This option has been used up
  843.         }
  844.         }
  845.     }
  846.     if (Escape)
  847.         break
  848.     # Do not delete arg until after processing of it, so that if it is not
  849.     # recognized it can be left in ARGV[].
  850.     delete argv[ArgNum]
  851.     ArgsLeft--
  852.     }
  853.     if (compress != 0) {
  854.     dest = 1
  855.     src = argc - ArgsLeft + 1
  856.     for (count = ArgsLeft - 1; count; count--) {
  857.         ARGV[dest] = ARGV[src]
  858.         dest++
  859.         src++
  860.     }
  861.     }
  862.     return ArgsLeft
  863. }
  864.  
  865. # Assignment to values in Options[] occurs only in this function.
  866. # Option: Option specifier character.
  867. # Value: Value to be assigned to option, if it takes a value.
  868. # Options[]: Options array to return values in.
  869. # ArgType: Argument type specifier character.
  870. # GotValue: Whether any value is available to be assigned to this option.
  871. # Name: Name of option being processed.
  872. # OptionNum: Number of this option (starting with 1) if set in argv[],
  873. #     or 0 if it was given in a config file or in the environment.
  874. # SingleOpt: true if the value (if any) that is available for this option was
  875. #     given as part of the same command line arg as the option.  Used only for
  876. #     options from the command line.
  877. # specGiven is the option specifier character use, if any (e.g. - or +),
  878. # for use in error messages.
  879. # Global variables: OptErr
  880. # Return value: negative value on error, 0 if option did not require an
  881. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  882. # the arg.
  883. # Current error values:
  884. # -1: Option that required an argument did not get it.
  885. # -2: Value of incorrect type supplied for option.
  886. # -3: Bad type given for option &
  887. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  888. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  889.     # If option takes a value...    [
  890.     NumTypes = "*()#<>]"
  891.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  892.     OptErr = "Bad type given for & option"
  893.     return -3
  894.     }
  895.  
  896.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  897.     if (!GotValue) {
  898.         if (Name != "")
  899.         OptErr = "Variable requires a value -- " Name
  900.         else
  901.         OptErr = "option requires an argument -- " Option
  902.         return -1
  903.     }
  904.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  905.         OptErr = Err
  906.         return -2
  907.     }
  908.     # Mark this as a numeric variable; will be propogated to Options[] val.
  909.     if (ArgType != ":" && ArgType != ";")
  910.         Value += 0
  911.     if ((Instance = ++Options[Option,"count"]) > 1)
  912.         Options[Option,Instance] = Value
  913.     else
  914.         Options[Option] = Value
  915.     }
  916.     # If this is an environ or rcfile assignment & it was given a value...
  917.     else if (!OptionNum && Value != "") {
  918.     UsedValue = 1
  919.     # If the value is "0" or "-" and this is the first instance of it,
  920.     # do not set Options[Option]; this allows an assignment in an rcfile to
  921.     # turn off an option (for the simple "Option in Options" test) in such
  922.     # a way that it cannot be turned on in a later file.
  923.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  924.         Instance = 1
  925.     else
  926.         Instance = ++Options[Option]
  927.     # Save the value even though this is a flag
  928.     Options[Option,Instance] = Value
  929.     }
  930.     # If this is a command line flag and has a - following it in the same arg,
  931.     # it is being turned off.
  932.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  933.     UsedValue = 2
  934.     if (Option in Options)
  935.         Instance = ++Options[Option]
  936.     else
  937.         Instance = 1
  938.     Options[Option,Instance]
  939.     }
  940.     # If this is a flag assignment without a value, increment the count for the
  941.     # flag unless it was turned off.  The indicator for a flag being turned off
  942.     # is that the flag index has not been set in Options[] but it has an
  943.     # instance count.
  944.     else if (Option in Options || !((Option,1) in Options))
  945.     # Increment number of times this flag seen; will inc null value to 1
  946.     Instance = ++Options[Option]
  947.     Options[Option,"num",Instance] = OptionNum
  948.     return UsedValue
  949. }
  950.  
  951. # Option is the option letter
  952. # Value is the value being assigned
  953. # Name is the var name of the option, if any
  954. # ArgType is one of:
  955. # :    String argument
  956. # ;    Non-null string argument
  957. # *    Floating point argument
  958. # (    Non-negative floating point argument
  959. # )    Positive floating point argument
  960. # #    Integer argument
  961. # <    Non-negative integer argument
  962. # >    Positive integer argument
  963. # specGiven is the option specifier character use, if any (e.g. - or +),
  964. # for use in error messages.
  965. # Returns null on success, err string on error
  966. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  967.     if (ArgType == ":")
  968.     return ""
  969.     if (ArgType == ";") {
  970.     if (Value == "")
  971.         Err = "must be a non-empty string"
  972.     }
  973.     # A number begins with optional + or -, and is followed by a string of
  974.     # digits or a decimal with digits before it, after it, or both
  975.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  976.     Err = "must be a number"
  977.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  978.     Err = "may not include a fraction"
  979.     else if (ArgType ~ "[()<>]" && Value < 0)
  980.     Err = "may not be negative"
  981.     # (
  982.     else if (ArgType ~ "[)>]" && Value == 0)
  983.     Err = "must be a positive number"
  984.     if (Err != "") {
  985.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  986.     if (Name != "")
  987.         return ErrStr "variable " substr(Name,1,1) " " Err
  988.     else {
  989.         if (Option == "&")
  990.         Option = Value
  991.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  992.     }
  993.     }
  994.     else
  995.     return ""
  996. }
  997.  
  998. # Note: only the above functions are needed by ProcArgs.
  999. # The rest of these functions call ProcArgs() and also do other
  1000. # option-processing stuff.
  1001.  
  1002. # Opts: Process command line arguments.
  1003. # Opts processes command line arguments using ProcArgs()
  1004. # and checks for errors.  If an error occurs, a message is printed
  1005. # and the program is exited.
  1006. #
  1007. # Input variables:
  1008. # Name is the name of the program, for error messages.
  1009. # Usage is a usage message, for error messages.
  1010. # OptList the option description string, as used by ProcArgs().
  1011. # MinArgs is the minimum number of non-option arguments that this
  1012. # program should have, non including ARGV[0] and +h.
  1013. # If the program does not require any non-option arguments,
  1014. # MinArgs should be omitted or given as 0.
  1015. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1016. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1017. # by the value of the environment variable HOME.  If a filename begins with
  1018. # $, the part from the character after the $ up until (but not including)
  1019. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1020. # environment; if found its value will be substituted, if not the filename will
  1021. # be discarded.
  1022. # rcfiles are read in the order given.
  1023. # Values given in them will not override values given on the command line,
  1024. # and values given in later files will not override those set in earlier
  1025. # files, because AssignVal() will store each with a different instance index.
  1026. # The first instance of each variable, either on the command line or in an
  1027. # rcfile, will be stored with no instance index, and this is the value
  1028. # normally used by programs that call this function.
  1029. # VarNames is a comma-separated list of variable names to map to options,
  1030. # in the same order as the options are given in OptList.
  1031. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1032. # searched for in the environment.  If set to -1, all values will be searched
  1033. # for in the environment.  Values given in the environment will override
  1034. # those given in the rcfiles but not those given on the command line.
  1035. # NoRCopt, if given, is an additional letter option that if given on the
  1036. # command line prevents the rcfiles from being read.
  1037. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1038. # ExclusiveOptions() for a description of exOpts.
  1039. # Special options:
  1040. # If x is made an option and is given, some debugging info is output.
  1041. # h is assumed to be the help option.
  1042.  
  1043. # Global variables:
  1044. # The command line arguments are taken from ARGV[].
  1045. # The arguments that are option specifiers and values are removed from
  1046. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1047. # The number of elements in ARGV[] should be in ARGC.
  1048. # After processing, ARGC is set to the number of elements left in ARGV[].
  1049. # The option values are put in Options[].
  1050. # On error, Err is set to a positive integer value so it can be checked for in
  1051. # an END block.
  1052. # Return value: The number of elements left in ARGV is returned.
  1053. # Must keep OptErr global since it may be set by InitOpts().
  1054. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1055. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1056.     if (MinArgs == "")
  1057.     MinArgs = 0
  1058.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1059.     optChars)
  1060.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1061.     if (ArgsLeft >= 0) {
  1062.         OptErr = "Not enough arguments"
  1063.         Err = 4
  1064.     }
  1065.     else
  1066.         Err = -ArgsLeft
  1067.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1068.     Name,OptErr,Usage > "/dev/stderr"
  1069.     exit 1
  1070.     }
  1071.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1072.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1073.     {
  1074.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1075.     Err = -e
  1076.     exit 1
  1077.     }
  1078.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1079.     {
  1080.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1081.     Err = 1
  1082.     exit 1
  1083.     }
  1084.     return ArgsLeft
  1085. }
  1086.  
  1087. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1088. # <variable-name><assignment-char><value>.
  1089. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1090. # line and whitespace between the variable name and the assignment character) 
  1091. # is stripped.  Lines that do not contain an assignment operator or which
  1092. # contain a null variable name are ignored, other than possibly being noted in
  1093. # the return value.  If more than one assignment is made to a variable, the
  1094. # first assignment is used.
  1095. # Input variables:
  1096. # File is the file to read.
  1097. # Comment is the line-comment character.  If it is found as the first non-
  1098. #     whitespace character on a line, the line is ignored.
  1099. # Assign is the assignment string.  The first instance of Assign on a line
  1100. #     separates the variable name from its value.
  1101. # If StripWhite is true, whitespace around the value (whitespace between the
  1102. #     assignment char and trailing whitespace on the line) is stripped.
  1103. # VarPat is a pattern that variable names must match.  
  1104. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1105. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1106. #     a line; no assignment operator is needed.  These variables are set in
  1107. #     the output array with a null value.  Lines containing nothing but
  1108. #     whitespace are still ignored.
  1109. # Output variables:
  1110. # Values[] contains the assignments, with the indexes being the variable names
  1111. #     and the values being the assigned values.
  1112. # Lines[] contains the line number that each variable occured on.  A flag set
  1113. #     is record by giving it an index in Lines[] but not in Values[].
  1114. # Return value:
  1115. # If any errors occur, a string consisting of descriptions of the errors
  1116. # separated by newlines is returned.  In no case will the string start with a
  1117. # numeric value.  If no errors occur,  the number of lines read is returned.
  1118. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1119. FlagsOK,
  1120. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1121.     if (Comment != "")
  1122.     Comment = "^" Comment
  1123.     AssignLen = length(Assign)
  1124.     if (VarPat == "")
  1125.     VarPat = "."    # null varname not allowed
  1126.     while ((Status = (getline Line < File)) == 1) {
  1127.     LineNum++
  1128.     sub("^[ \t]+","",Line)
  1129.     if (Line == "")        # blank line
  1130.         continue
  1131.     if (Comment != "" && Line ~ Comment)
  1132.         continue
  1133.     if (Pos = index(Line,Assign)) {
  1134.         Var = substr(Line,1,Pos-1)
  1135.         Val = substr(Line,Pos+AssignLen)
  1136.         if (StripWhite) {
  1137.         sub("^[ \t]+","",Val)
  1138.         sub("[ \t]+$","",Val)
  1139.         }
  1140.     }
  1141.     else {
  1142.         Var = Line    # If no value, var is entire line
  1143.         Val = ""
  1144.     }
  1145.     if (!FlagsOK && Val == "") {
  1146.         Errs = Errs \
  1147.         sprintf("\nBad assignment on line %d of file %s: %s",
  1148.         LineNum,File,Line)
  1149.         continue
  1150.     }
  1151.     sub("[ \t]+$","",Var)
  1152.     if (Var !~ VarPat) {
  1153.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1154.         LineNum,File,Var)
  1155.         continue
  1156.     }
  1157.     if (!(Var in Lines)) {
  1158.         Lines[Var] = LineNum
  1159.         if (Pos)
  1160.         Values[Var] = Val
  1161.     }
  1162.     }
  1163.     if (Status)
  1164.     Errs = Errs "\nCould not read file " File
  1165.     close(File)
  1166.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1167. }
  1168.  
  1169. # Variables:
  1170. # Data is stored in Options[].
  1171. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1172. # Global vars:
  1173. # Sets OptErr.  Uses ENVIRON[].
  1174. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1175. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1176. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1177. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1178.     split("",filesRead,"")    # make awk know this is an array
  1179.     NumVars = split(VarNames,Vars,",")
  1180.     TypesInd = Ret = 0
  1181.     if (EnvSearch == -1)
  1182.     EnvSearch = NumVars
  1183.     for (i = 1; i <= NumVars; i++) {
  1184.     Var = Vars[i]
  1185.     CharOpt = substr(OptList,++TypesInd,1)
  1186.     if (CharOpt ~ "^[:;*()#<>&]$")
  1187.         CharOpt = substr(OptList,++TypesInd,1)
  1188.     Map[Var] = CharOpt
  1189.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1190.     # Do not overwrite entries from environment
  1191.     if (i <= EnvSearch && Var in ENVIRON &&
  1192.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1193.         return Err
  1194.     }
  1195.  
  1196.     numrcFiles = split(rcFiles,fNames,":")
  1197.     for (i = 1; i <= numrcFiles; i++) {
  1198.     rcFile = fNames[i]
  1199.     if (rcFile ~ "^~/")
  1200.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1201.     else if (rcFile ~ /^\$/) {
  1202.         rcFile = substr(rcFile,2)
  1203.         match(rcFile,"^[a-zA-Z0-9_]*")
  1204.         envvar = substr(rcFile,1,RLENGTH)
  1205.         if (envvar in ENVIRON)
  1206.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1207.         else
  1208.         continue
  1209.     }
  1210.     if (rcFile in filesRead)
  1211.         continue
  1212.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1213.     # may be the same
  1214.     filesRead[rcFile]
  1215.     if ("x" in Options)
  1216.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1217.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1218.     if (retStr > 0)
  1219.         READ_RCFILE = 1
  1220.     else if (ret != "") {
  1221.         OptErr = retStr
  1222.         Ret = -1
  1223.     }
  1224.     for (Var in Lines)
  1225.         if (Var in Map) {
  1226.         if ((Err = AssignVal(Map[Var],
  1227.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1228.         Var in Values,Var,0)) < 0)
  1229.             return Err
  1230.         }
  1231.         else {
  1232.         OptErr = sprintf(\
  1233.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1234.         Lines[Var],rcFile)
  1235.         Ret = -1
  1236.         }
  1237.     }
  1238.  
  1239.     if ("x" in Options)
  1240.     for (Var in Map)
  1241.         if (Map[Var] in Options)
  1242.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1243.         "/dev/stderr"
  1244.         else
  1245.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1246.     return Ret
  1247. }
  1248.  
  1249. # OptSets is a semicolon-separated list of sets of option sets.
  1250. # Within a list of option sets, the option sets are separated by commas.  For
  1251. # each set of sets, if any option in one of the sets is in Options[] AND any
  1252. # option in one of the other sets is in Options[], an error string is returned.
  1253. # If no conflicts are found, nothing is returned.
  1254. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1255. # the exclusions presented by the first set of sets (ab,def,g) if:
  1256. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1257. # (a or b is in Options[]) AND (g is in Options) OR
  1258. # (d, e, or f is in Options[]) AND (g is in Options)
  1259. # An error will be returned due to the exclusions presented by the second set
  1260. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1261. # todo: make options given on command line unset options given in config file
  1262. # todo: that they conflict with.
  1263. function ExclusiveOptions(OptSets,Options,
  1264. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1265. SetNum,OSetNum) {
  1266.     NumSetSets = split(OptSets,SetSets,";")
  1267.     # For each set of sets...
  1268.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1269.     # NumSets is the number of sets in this set of sets.
  1270.     NumSets = split(SetSets[SetSet],Sets,",")
  1271.     # For each set in a set of sets except the last...
  1272.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1273.         s1 = Sets[SetNum]
  1274.         L1 = length(s1)
  1275.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1276.         # If any of the options in this set was given, check whether
  1277.         # any of the options in the other sets was given.  Only check
  1278.         # later sets since earlier sets will have already been checked
  1279.         # against this set.
  1280.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1281.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1282.             s2 = Sets[OSetNum]
  1283.             L2 = length(s2)
  1284.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1285.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1286.                 ErrStr = ErrStr "\n"\
  1287.                 sprintf("Cannot give both %s and %s options.",
  1288.                 c1,c2)
  1289.             }
  1290.     }
  1291.     }
  1292.     if (ErrStr != "")
  1293.     return substr(ErrStr,2)
  1294.     return ""
  1295. }
  1296.  
  1297. # The value of each instance of option Opt that occurs in Options[] is made an
  1298. # index of Set[].
  1299. # The return value is the number of instances of Opt in Options.
  1300. function Opt2Set(Options,Opt,Set,  count) {
  1301.     if (!(Opt in Options))
  1302.     return 0
  1303.     Set[Options[Opt]]
  1304.     count = Options[Opt,"count"]
  1305.     for (; count > 1; count--)
  1306.     Set[Options[Opt,count]]
  1307.     return count
  1308. }
  1309.  
  1310. # The value of each instance of option Opt that occurs in Options[] that
  1311. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1312. # Other values are made indexes of Set[].
  1313. # The return value is the number of instances of Opt in Options.
  1314. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1315.     ret = Opt2Set(Options,Opt,aSet)
  1316.     for (value in aSet)
  1317.     if (substr(value,1,1) == "!")
  1318.         nSet[substr(value,2)]
  1319.     else
  1320.         Set[value]
  1321.     return ret
  1322. }
  1323.  
  1324. # Returns true if option Opt was given on the command line.
  1325. function CmdLineOpt(Options,Opt,  i) {
  1326.     for (i = 1; (Opt,"num",i) in Options; i++)
  1327.     if (Options[Opt,"num",i] != 0)
  1328.         return 1
  1329.     return 0
  1330. }
  1331. ### End of ProcArgs library
  1332. ### Begin qsort routines
  1333.  
  1334. # Arr[] is an array of values with arbitrary indices.
  1335. # k[] is returned with numeric indices 1..n.
  1336. # The values in k[] are the indices of Arr[],
  1337. # ordered so that if Arr[] is stepped through
  1338. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1339. # through in order of the values of its elements.
  1340. # The return value is the number of elements in the arrays (n).
  1341. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1342.     ElNum = 0
  1343.     for (ArrInd in Arr)
  1344.     k[++ElNum] = ArrInd
  1345.     qsortSegment(Arr,k,1,ElNum)
  1346.     return ElNum
  1347. }
  1348.  
  1349. # Sort a segment of an array.
  1350. # Arr[] contains data with arbitrary indices.
  1351. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1352. # This function sorts the elements of arr that are pointed to by
  1353. # k[start..end], swapping the values of elements of k[] so that
  1354. # when this function returns arr[k[start..end]] will be in order.
  1355. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1356.     # handle two-element case explicitly for a tiny speedup
  1357.     if ((end - start) == 1) {
  1358.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1359.         k[start] = tmpe
  1360.         k[end] = tmps
  1361.     }
  1362.     return
  1363.     }
  1364.     # Make sure comparisons act on these as numbers
  1365.     left = start+0
  1366.     right = end+0
  1367.     sepval = Arr[k[int((left + right) / 2)]]
  1368.     # Make every element <= sepval be to the left of every element > sepval
  1369.     while (left < right) {
  1370.     while (Arr[k[left]] < sepval)
  1371.         left++
  1372.     while (Arr[k[right]] > sepval)
  1373.         right--
  1374.     if (left < right) {
  1375.         tmp = k[left]
  1376.         k[left++] = k[right]
  1377.         k[right--] = tmp
  1378.     }
  1379.     }
  1380.     if (left == right)
  1381.     if (Arr[k[left]] < sepval)
  1382.         left++
  1383.     else
  1384.         right--
  1385.     if (start < right)
  1386.     qsortSegment(Arr,k,start,right)
  1387.     if (left < end)
  1388.     qsortSegment(Arr,k,left,end)
  1389. }
  1390.  
  1391. # Arr[] is an array of values with arbitrary indices.
  1392. # k[] is returned with numeric indices 1..n.
  1393. # The values in k are the indices of Arr[],
  1394. # ordered so that if Arr[] is stepped through
  1395. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1396. # through in order of the values of its indices.
  1397. # The return value is the number of elements in the arrays (n).
  1398. # If the indexes are numeric, Numeric should be true, so that they can be
  1399. # compared as such rather than as strings.  Numeric indexes do not have to be
  1400. # contiguous.
  1401. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1402.     ElNum = 0
  1403.     if (Numeric)
  1404.     # Indexes do not preserve numeric type, so must be forced
  1405.     for (ArrInd in Arr)
  1406.         k[++ElNum] = ArrInd+0
  1407.     else
  1408.     for (ArrInd in Arr)
  1409.         k[++ElNum] = ArrInd
  1410.     qsortNumIndByValue(k,1,ElNum)
  1411.     return ElNum
  1412. }
  1413.  
  1414. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1415. # by value.
  1416. # start and end are the starting and ending indexes of the range to be sorted.
  1417. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1418.     # handle two-element case explicitly for a tiny speedup
  1419.     if ((start - end) == 1) {
  1420.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1421.         Arr[start] = tmpe
  1422.         Arr[end] = tmps
  1423.     }
  1424.     return
  1425.     }
  1426.     left = start+0
  1427.     right = end+0
  1428.     sepval = Arr[int((left + right) / 2)]
  1429.     while (left < right) {
  1430.     while (Arr[left] < sepval)
  1431.         left++
  1432.     while (Arr[right] > sepval)
  1433.         right--
  1434.     if (left <= right) {
  1435.         tmp = Arr[left]
  1436.         Arr[left++] = Arr[right]
  1437.         Arr[right--] = tmp
  1438.     }
  1439.     }
  1440.     if (start < right)
  1441.     qsortNumIndByValue(Arr,start,right)
  1442.     if (left < end)
  1443.     qsortNumIndByValue(Arr,left,end)
  1444. }
  1445.  
  1446. ### End qsort routines
  1447.  
  1448. # If Command is non-null, run Command and read lines from it until it closes
  1449. # its output.  Otherwise, read File to the end.
  1450. # Put the lines in Lines indexed by the FieldNum'th field; if a line does not
  1451. # have at least FieldNum fields, the line is stored under the null index.
  1452. # If FieldNum is <=0, the indexing field is the FieldNum'th from the last;
  1453. # if a lines does not have at least -FieldNum+1 fields, the line is stored
  1454. # under the null index.
  1455. # If more than one line is read has a given index, the 2nd and later lines are
  1456. # appended to the indexed value separated from it by a preceding newline.
  1457. function ReadLines(Command,File,FieldNum,Sep,Lines,Debug,
  1458. Line,Elem,ret,i) {
  1459.     if (Debug)
  1460.     if (Command != "")
  1461.         print "Issuing command: " Command > "/dev/stderr"
  1462.     else
  1463.         print "Reading file: " File > "/dev/stderr"
  1464.     i = FieldNum
  1465.     while ((ret = \
  1466.     (Command != "" ? (Command | getline Line) : (getline Line < File))) == 1) {
  1467.     split(Line,Elem,Sep)
  1468.     if (FieldNum <= 0)
  1469.         i = NF - FieldNum
  1470.     if (i < 1 || NF < FieldNum)
  1471.         if ("" in Lines)
  1472.         Lines[""] = Lines[""] "\n" Line
  1473.         else
  1474.         Lines[""] = Line
  1475.     if (Debug)
  1476.         print "Output line, indexed by field " i " (" Elem[i] "):\n" \
  1477.         Line > "/dev/stderr"
  1478.     if (Elem[i] in Lines)
  1479.         Lines[Elem[i]] = Lines[Elem[i]] "\n" Line
  1480.     else
  1481.         Lines[Elem[i]] = Line
  1482.     }
  1483.     close(Command != "" ? Command : File)
  1484.     return ret
  1485. }
  1486.